#include "ipa2.h"
#include "ui_ipa2.h"

IPA2::IPA2(QWidget *parent) : QMainWindow(parent), ui(new Ui::IPA2),
    redBrush(QBrush(Qt::red)), greenBrush(QBrush(Qt::green)), blueBrush(QBrush(Qt::blue)),
    redPen(QPen(Qt::red)), greenPen(QPen(Qt::green)), bluePen(QPen(Qt::blue))
{
    ui->setupUi(this);

    colorButtonGroup = new QButtonGroup(ui->ColorGroupBox);
    shapeButtonGroup = new QButtonGroup(ui->ShapeGroupBox);

    colorButtonGroup->addButton(ui->RedButton);
    colorButtonGroup->addButton(ui->GreenButton);
    colorButtonGroup->addButton(ui->BlueButton);
    shapeButtonGroup->addButton(ui->CircleButton);
    shapeButtonGroup->addButton(ui->SquareButton);
    shapeButtonGroup->addButton(ui->TriangleButton);

    connect(ui->NewAction, SIGNAL(triggered()), this, SLOT(newFile()));
    connect(ui->OpenAction, SIGNAL(triggered()), this, SLOT(openFile()));
    connect(ui->SaveAction, SIGNAL(triggered()), this, SLOT(saveFile()));
    connect(ui->SaveAsAction, SIGNAL(triggered()), this, SLOT(saveAsFile()));
    connect(ui->ExitAction, SIGNAL(triggered()), this, SLOT(exit()));
    connect(ui->CircleAction, SIGNAL(triggered()), this, SLOT(setCircleButton()));
    connect(ui->SquareAction, SIGNAL(triggered()), this, SLOT(setSquareButton()));
    connect(ui->TriangleAction, SIGNAL(triggered()), this, SLOT(setTriangleButton()));
    connect(ui->RedAction, SIGNAL(triggered()), this, SLOT(setRedButton()));
    connect(ui->BlueAction, SIGNAL(triggered()), this, SLOT(setBlueButton()));
    connect(ui->GreenAction, SIGNAL(triggered()), this, SLOT(setGreenButton()));
    connect(ui->AboutAction, SIGNAL(triggered()), this, SLOT(about()));

    //Subtracting 2 from the width and height is necessary, for reasons unknown to me.
    //If the subtraction is omitted, then the GraphicsView will have scrollbars on the right and bottom.
    scene.setSceneRect(0, 0, ui->GraphicsView->width() - 2, ui->GraphicsView->width() - 2);
    ui->GraphicsView->setScene(&scene);
    ui->GraphicsView->setRenderHint(QPainter::Antialiasing);
    ui->GraphicsView->show();

    tempShape = NULL;
    mousePointerInWidget = false;
    filePath = QString("");
    drawingModified = false;
}

IPA2::~IPA2()
{
    delete ui;
    delete colorButtonGroup;
    delete shapeButtonGroup;
}

std::string IPA2::buildCoordinatesString(int x, int y)
{
    StringBuilder builder;
    builder.add("(");
    builder.add(x);
    builder.add(", ");
    builder.add(y);
    builder.add(")");

    return builder.toString();
}

bool IPA2::event(QEvent *event)
{
    if(event->type() == QEvent::HoverMove)
    {
        QHoverEvent *hoverEvent = static_cast<QHoverEvent *>(event);
        QPoint qtMousePosition = hoverEvent->pos();

        unsigned int relativeX = qtMousePosition.x() - ui->GraphicsView->x();
        unsigned int relativeY = ui->GraphicsView->width() - (qtMousePosition.y() - ui->GraphicsView->y());

        if(relativeX <= ui->GraphicsView->width() && relativeY <= ui->GraphicsView->width())
        {
            mousePointerInWidget = true;

            std::string coordinateString = buildCoordinatesString(relativeX, relativeY);
            const char *coordinateStringCString = coordinateString.c_str();
            ui->MouseCoordsLabel->setText(coordinateStringCString);

            draw(relativeX, relativeY);
        }
        else
        {
            mousePointerInWidget = false;
        }
    }
    else if(event->type() == QEvent::MouseButtonPress)
    {
        if(mousePointerInWidget)
        {
            drawingModified = true;
            saveCurrentShape();
        }
    }

    return QMainWindow::event(event);
}

void IPA2::closeEvent(QCloseEvent *event)
{
    if(exit())
    {
        event->accept();
    }
    else
    {
        event->ignore();
    }
}

void IPA2::saveCurrentShape()
{
    savedShapes.push_back(tempShape);
    tempShape = NULL;
}

void IPA2::drawSerializedShape(SerializedShape shape)
{
    int x = shape.getX() + SHAPE_SIZE;
    int y = ui->GraphicsView->width() - (shape.getY() + SHAPE_SIZE);

    QColor color = QColor(shape.getRed(), shape.getGreen(), shape.getBlue());
    QPen pen = QPen(color);
    QBrush brush = QBrush(color);

    switch(shape.getType())
    {
        default:
            throw UnknownTypeException();
            break;

        case(QGraphicsRectItem::Type):
            drawSquare(x, y, brush, pen);
            break;

        case(QGraphicsEllipseItem::Type):
            drawCircle(x, y, brush, pen);
            break;

        case(QGraphicsPolygonItem::Type):
            drawTriangle(x, y, brush, pen);
            break;
    }

    saveCurrentShape();
}

void IPA2::drawTriangle(int x, int y, const QBrush &brush, const QPen &pen)
{
    QPolygonF triangle;
    triangle.append(QPointF(x - SHAPE_SIZE, ui->GraphicsView->width() - y));
    triangle.append(QPointF(x, ui->GraphicsView->width() - (y + SHAPE_SIZE)));
    triangle.append(QPointF(x + SHAPE_SIZE, ui->GraphicsView->width() - y));

    tempShape = scene.addPolygon(triangle, pen, brush);
}

void IPA2::drawSquare(int x, int y, const QBrush &brush, const QPen &pen)
{
    tempShape = scene.addRect(x - SHAPE_SIZE, (ui->GraphicsView->width() - y) - SHAPE_SIZE, 2*SHAPE_SIZE, 2*SHAPE_SIZE, pen, brush);
}

void IPA2::drawCircle(int x, int y, const QBrush &brush, const QPen &pen)
{
    tempShape = scene.addEllipse(x - SHAPE_SIZE, (ui->GraphicsView->width() - y) - SHAPE_SIZE, 2*SHAPE_SIZE, 2*SHAPE_SIZE, pen, brush);
}

void IPA2::draw(int x, int y)
{
    try
    {
        std::pair<const QBrush *, const QPen *> brushAndPen = getBrushAndPen();
        const QBrush &brush = *brushAndPen.first;
        const QPen &pen = *brushAndPen.second;

        if(tempShape != NULL)
        {
            scene.removeItem(tempShape);
        }

        if(ui->TriangleButton->isChecked())
        {
            drawTriangle(x, y, brush, pen);
        }
        else if(ui->SquareButton->isChecked())
        {
            drawSquare(x, y, brush, pen);
        }
        else if(ui->CircleButton->isChecked())
        {
            drawCircle(x, y, brush, pen);
        }
    }
    catch(NoColorSelectedException)
    {
        return;
    }
}

std::pair<const QBrush *, const QPen *> IPA2::getBrushAndPen()
{
    std::pair<const QBrush *, const QPen *> brushPenPair;

    if(ui->RedButton->isChecked())
    {
        brushPenPair.first = &redBrush;
        brushPenPair.second = &redPen;
    }
    else if(ui->BlueButton->isChecked())
    {
        brushPenPair.first = &blueBrush;
        brushPenPair.second = &bluePen;
    }
    else if(ui->GreenButton->isChecked())
    {
        brushPenPair.first = &greenBrush;
        brushPenPair.second = &greenPen;
    }
    else
    {
        throw NoColorSelectedException();
    }

    return brushPenPair;
}

void IPA2::newFile()
{
    if(drawingModified)
    {
        int warningBoxReturnCode = unsavedWarningBox();

        switch(warningBoxReturnCode)
        {
            default:
                throw UnknownReturnCodeException();
                break;

            case SAVE:
                saveFile();
                break;

            case CANCEL:
                return;
                break;

            case DISCARD:
                break;
        }
    }

    QList<QGraphicsItem *> savedItems = scene.items();
    foreach(QGraphicsItem *toRemove, savedItems)
    {
        scene.removeItem(toRemove);
    }

    tempShape = NULL;
    savedShapes.clear();
    drawingModified = false;

    filePath = QFileDialog::getSaveFileName(this, tr("Choose a file name to save new drawing as"), "~/", tr("Drawing File (*.pav)"));

    if(!filePath.endsWith((".pav")))
    {
        filePath += ".pav";
    }
}

void IPA2::openFile()
{
    filePath = QFileDialog::getOpenFileName(this, tr("Choose a drawing file to open"), "~/", tr("Drawing File (*.pav)"));

    if(!filePath.isNull())
    {
        std::ifstream inputFile(filePath.toAscii());
        std::string inputFileString = std::string((std::istreambuf_iterator<char>(inputFile)),
                                                   std::istreambuf_iterator<char>());
        inputFile.close();

        QStringList lineList = QString(inputFileString.c_str()).split("\n");

        foreach(QString line, lineList)
        {
            if(!line.isEmpty())
            {
                SerializedShape shape = SerializedShape(line);
                drawSerializedShape(shape);
            }
        }
    }
}

std::string IPA2::createSerializedShapeFileString()
{
    StringBuilder builder;

    for(std::vector<QAbstractGraphicsShapeItem *>::iterator it = savedShapes.begin(); it < savedShapes.end(); it++)
    {
        SerializedShape shape = SerializedShape(*it);
        builder.add(shape.toString());
    }

    return builder.toString();
}

void IPA2::saveFile()
{
    if(filePath.isEmpty())
    {
        saveAsFile();
    }
    else
    {
        std::string saveFileString = createSerializedShapeFileString();
        std::ofstream outputFile;
        outputFile.open(filePath.toStdString().c_str());
        outputFile << saveFileString;
        outputFile.close();

        drawingModified = false;
    }
}

void IPA2::saveAsFile()
{
    filePath = QFileDialog::getSaveFileName(this, tr("Choose a file name to save current drawing as"), "~/", tr("Drawing File (*.pav)"));

    if(!filePath.endsWith((".pav")))
    {
        filePath += ".pav";
    }

    saveFile();
}

int IPA2::unsavedWarningBox()
{
    int returnCode = -1;
    QMessageBox *warningBox = new QMessageBox(this);
    warningBox->setWindowTitle("Warning");
    warningBox->setText("Save changes to current drawing before closing?");

    QPushButton *saveButton = warningBox->addButton("Save", QMessageBox::ActionRole);
    QPushButton *discardButton = warningBox->addButton("Discard", QMessageBox::ActionRole);
    QPushButton *cancelButton = warningBox->addButton("Cancel", QMessageBox::ActionRole);

    warningBox->exec();

    QPushButton *clickedButton = reinterpret_cast<QPushButton *>(warningBox->clickedButton());
    if(clickedButton == saveButton)
    {
        returnCode = SAVE;
    }
    else if(clickedButton == cancelButton)
    {
        returnCode = CANCEL;
    }
    else if(clickedButton == discardButton)
    {
        returnCode = DISCARD;
    }

    delete warningBox;
    return returnCode;
}

bool IPA2::exit()
{
    bool toExit = true;

    if(drawingModified)
    {
        int warningBoxReturnCode = unsavedWarningBox();

        switch(warningBoxReturnCode)
        {
            default:
                throw UnknownReturnCodeException();
                break;

            case SAVE:
                saveFile();
                break;

            case CANCEL:
                toExit = false;
                break;

            case DISCARD:
                break;
        }
    }
    if(toExit)
    {
        QApplication::exit(0);
    }

    return toExit;
}

void IPA2::setCircleButton()
{
    ui->CircleButton->setChecked(true);
    ui->SquareButton->setChecked(false);
    ui->TriangleButton->setChecked(false);
}

void IPA2::setSquareButton()
{
    ui->CircleButton->setChecked(false);
    ui->SquareButton->setChecked(true);
    ui->TriangleButton->setChecked(false);
}

void IPA2::setTriangleButton()
{
    ui->CircleButton->setChecked(false);
    ui->SquareButton->setChecked(false);
    ui->TriangleButton->setChecked(true);
}

void IPA2::setRedButton()
{
    ui->RedButton->setChecked(true);
    ui->GreenButton->setChecked(false);
    ui->BlueButton->setChecked(false);
}

void IPA2::setGreenButton()
{
    ui->RedButton->setChecked(false);
    ui->GreenButton->setChecked(true);
    ui->BlueButton->setChecked(false);
}

void IPA2::setBlueButton()
{
    ui->RedButton->setChecked(false);
    ui->GreenButton->setChecked(false);
    ui->BlueButton->setChecked(true);
}


void IPA2::about()
{
    QMessageBox *aboutBox = new QMessageBox(this);
    aboutBox->setWindowTitle("About");
    aboutBox->setText("IPA2\nWritten by John Pavlick\n");
    aboutBox->exec();

    delete aboutBox;
}
